Skip to content

feat: 콘텐츠 관리 - 콘텐츠 수정, 삭제 기능 구현#157

Merged
plzslp merged 25 commits into
devfrom
feature/11-content-update
Jun 26, 2026
Merged

feat: 콘텐츠 관리 - 콘텐츠 수정, 삭제 기능 구현#157
plzslp merged 25 commits into
devfrom
feature/11-content-update

Conversation

@plzslp

@plzslp plzslp commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

관련 이슈

작업 내용

  • 콘텐츠 수정, 삭제 기능을 구현하였습니다.
  • 콘텐츠 수정, 삭제 기능에 대한 Controller, Service 테스트 코드를 작성하였습니다.
  • 콘텐츠 Repository 조회 메서드에 대해 테스트 코드를 작성하였습니다.
  • binary_contents 업로드 상태에 DELETED를 추가하였습니다.
  • 콘텐츠 생성/수정 요청시 태그 최대 개수를 10개로 지정하여 무분별한 태그 남용을 방지했습니다.

변경 사항

  • upload_status에 DELETED 상태 추가
  • N+1 문제 방지로 User 프로필 업데이트 로직에 findById() 메서드 호출 대신 findWithProfileImageById() 메서드 호출로 변경

테스트 내용

  • 테스트 코드 작성
  • 로컬 테스트 완료
  • API 테스트 완료
  • 기타 테스트 완료

체크리스트

  • 코드 컨벤션을 지켰습니다.
  • 불필요한 코드를 제거했습니다.
  • 주석이 필요한 부분에 추가했습니다.
  • 관련 문서를 수정했습니다.
  • 리뷰어가 이해하기 쉽도록 작성했습니다.

리뷰 포인트

  • 중점적으로 봐줬으면 하는 부분을 작성해주세요.

스크린샷 / 참고 자료

  • 필요한 경우 첨부해주세요.

🔗 관련 이슈

  • close #11
  • close #18
  • close #101
  • close #103

📝 작업 내용

기존에는 콘텐츠 생성 기능은 어느 정도 흐름이 있었지만, 운영에서 필요한 콘텐츠 수정/삭제가 API-도메인-서비스-예외-테스트 전반에서 일관되게 연결되지 못했습니다. 특히

  • 썸네일을 교체/삭제할 때 “어떤 상태로 기존 리소스를 정리 대상임을 표현할지”
  • 수정 요청에서 **태그 검증(과도한 태그 사용 방지)**을 표준화하는 방식
  • 예외 응답의 구체성(식별자/상세 정보 포함)
  • 관련 엔티티 로딩에서의 N+1 등 조회 성능 문제
    가 정리되어 있지 않았습니다.

이번 PR은 콘텐츠 수정/삭제 요청이 실제 도메인 갱신 및 리소스 정리 흐름으로 이어지도록, DTO/예외/오픈API 및 테스트까지 함께 보강했습니다. 또한 기존 썸네일 리소스의 정리 흐름을 명시하기 위해 binary_contents 업로드 상태 체계를 확장했습니다.

주요 변경사항

  1. 콘텐츠 수정/삭제 API 계약 확장 및 문서 정리

    • ContentControllerPATCH /contents/{contentId}(제목/설명/태그 + 선택적 thumbnail)와 DELETE /contents/{contentId}를 추가했습니다.
    • ContentApi OpenAPI 스키마(요청 multipart 스키마/응답 오류 스키마)를 수정/삭제 계약에 맞춰 정리했습니다.
  2. 기존 썸네일 교체/삭제 시 “정리 상태”를 업로드 상태로 모델링

    • BinaryContentUploadStatusDELETED 상태를 추가하고, DB 마이그레이션으로 binary_contents의 CHECK 제약에 DELETED를 허용하도록 갱신했습니다.
    • ContentService.update(...)/delete(...)에서 기존 썸네일이 존재하면 해당 BinaryContent의 업로드 상태를 DELETED로 전환한 뒤 후속 처리를 진행하도록 변경했습니다.
    • storeThumbnailImage(...)로 썸네일 저장 및 업로드 이벤트 발행 흐름을 정리하고, 업데이트 시에는 새 썸네일이 들어온 경우에만 첨부를 수행하도록 분기했습니다.
    • (후속 비동기/정리 처리의 실제 연계는 raw_summary 기준으로 TODO가 존재하므로) 현재는 “상태 모델링 및 전환”까지가 명확한 범위로 확인됩니다.
  3. 태그 사용량 제한 및 예외 의미 강화

    • 콘텐츠 요청의 태그는 최대 10개로 제한하도록 DTO 검증을 추가/강화했습니다.
    • 태그 정규화 및 초과 검증을 헬퍼/예외로 분리하여, TooManyTagsException(400)로 명확히 실패하도록 처리했습니다.
    • 제목 검증 실패는 InvalidContentTitleException으로 구체화했고, 예외 처리에서 details(상세 정보) 포함 가능하도록 ContentException 생성자 형태를 확장했습니다.
    • ContentNotFoundExceptioncontentId를 포함해 반환하도록 변경했습니다.
  4. 도메인/서비스 흐름 및 조회 성능 개선

    • 콘텐츠 엔티티의 업데이트 로직에서 StringUtils.hasText 기반 제목 검증을 도메인 레벨로 반영하는 식으로 흐름을 정리했습니다.
    • N+1 완화를 위해 사용자 프로필 조회를 findById()에서 findWithProfileImageById()로 변경했습니다.
    • 콘텐츠/워칭 관련 repository의 @EntityGraph 범위를 확장해 content.thumbnail, content.contentTags, content.contentTags.tag까지 함께 로딩되도록 조정했습니다.
  5. 테스트 보강(Controller/Service/Repository)

    • ContentControllerTest: PATCH/DELETE의 정상 케이스 및 400/404/500 예외 응답(유효성/존재 여부/서비스 오류)을 함께 검증하도록 확장했습니다.
    • ContentServiceTest: 썸네일 유무/교체/삭제 시 DELETED 상태 전환 및 의존성 호출 여부, 태그 검증 실패 및 존재하지 않는 콘텐츠 처리까지 시나리오별로 검증했습니다.
    • ContentRepositoryTest: findWithStatsAndTagsById가 필요한 연관 데이터를 fetch join으로 가져오고 쿼리 수를 의도대로 유지하는지 검증했습니다.
    • UserServiceTest: 프로필 업데이트 테스트 조회 경로를 findWithProfileImageById로 맞추도록 조정했습니다.

리뷰 시 확인 권장: 썸네일 “기존 리소스 상태 전환(DELETED)” 이후 실제 정리(예: 저장소/배치/비동기 소비)까지 end-to-end로 연결되는 지점이 raw_summary 기준으로는 완전히 확인되지 않으므로, 해당 TODO/후속 로직 존재 여부를 함께 점검하는 것이 좋습니다. 또한 태그 갱신 시(유지/교체/추가) 의도한 변경만 반영되는지(정규화 기준 및 조회/저장 범위) 구현 디테일을 확인해 주세요.

📊 변경 효과 요약 (가능한 경우에만)

  • 테스트는 ContentRepositoryTest 신규 추가 1건, ContentControllerTest/ContentServiceTest 확장, UserServiceTest 수정까지 포함해 수정/삭제 기능의 핵심 시나리오를 넓혔습니다.
  • 정량 집계(전체 변경 라인 합산, 테스트 케이스 총 수 등)는 raw_summary만으로 일관되게 산정하지 못해 측정하지 않음으로 표기합니다.

plzslp added 21 commits June 26, 2026 11:06
- 기존 썸네일 교체 시 상태 DELETED로 변경
- UserRepository: N+1 문제 방지 update용 조회 메서드 추가
@plzslp plzslp self-assigned this Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9dc4b4ea-cafd-4571-a86c-fa5d22724943

📥 Commits

Reviewing files that changed from the base of the PR and between 466307d and 3dc5faf.

📒 Files selected for processing (1)
  • src/main/java/com/codeit/team5/mopl/content/entity/Content.java

📝 Walkthrough

Walkthrough

콘텐츠 수정·삭제용 PATCH/DELETE 엔드포인트와 관련 DTO, 예외, 서비스 로직, OpenAPI 문서, 테스트가 추가되었다. 썸네일 교체·삭제 시 업로드 상태 DELETED를 사용하도록 바뀌었고, 태그 수 제한이 10개로 추가되었다. 사용자 서비스는 프로필 이미지를 포함해 조회하도록 변경되었으며, 관련 저장소 조회와 테스트가 함께 갱신되었다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning User 프로필 업데이트 변경과 콘텐츠 생성 태그 제한처럼 직접 요구되지 않은 변경이 섞여 있습니다. 무관한 UserService/UserRepository 변경은 별도 PR로 분리하고, 이 PR에는 콘텐츠 수정/삭제 범위의 변경만 남기세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 수정/삭제 기능 구현이라는 핵심 변경을 짧고 명확하게 드러내는 제목입니다.
Description check ✅ Passed 요구 템플릿의 관련 이슈, 작업 내용, 변경 사항, 테스트, 체크리스트, 리뷰 포인트를 대부분 갖추고 있습니다.
Linked Issues check ✅ Passed 콘텐츠 수정/삭제 엔드포인트와 비즈니스 로직, 관련 테스트가 추가되어 핵심 요구사항을 충족합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/11-content-update

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/com/codeit/team5/mopl/user/service/UserServiceTest.java (1)

199-227: 📐 Maintainability & Code Quality | 🔵 Trivial

기존 프로필 이미지 교체 시 DELETED 전환 테스트를 추가하세요.

현재 update_withImage_success()User.create(...)로 시작해서 profileImage == null 상태입니다. 그래서 oldProfile != null 분기가 실행되지 않아, 기존 이미지를 새 이미지로 바꿀 때 이전 BinaryContentDELETED가 되는 핵심 동작이 빠져 있습니다.

  • 권장: 기존 profileImage를 가진 사용자로 교체 후 oldProfile.getUploadStatus() == DELETED를 단언하는 별도 테스트 추가
    • 장점: 누락된 분기를 직접 커버해 회귀 방지에 가장 유리
    • 단점: 테스트가 하나 늘어남
  • 대안: 기존 update_withImage_success()에 교체 시나리오를 섞기
    • 장점: 변경량이 적음
    • 단점: 신규 첨부와 교체 케이스가 섞여 원인 분리가 어려움

ContentServiceTest에 같은 패턴의 테스트가 있으니, 사용자 프로필 이미지도 같은 방식으로 맞추는 편이 자연스럽습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/codeit/team5/mopl/user/service/UserServiceTest.java` around
lines 199 - 227, 현재 update_withImage_success()는 새 프로필 이미지만 붙는 경우만 검증해서, 기존 이미지가
있던 사용자의 교체 시 oldProfile이 DELETED로 전환되는 분기를 커버하지 못합니다. UserService.update와 관련된
프로필 이미지 교체 경로를 직접 검증하는 별도 테스트를 추가하거나 기존 테스트를 교체 시나리오로 바꿔서, User.create 대신 기존
profileImage를 가진 User를 준비한 뒤 oldProfile.getUploadStatus()가 DELETED로 바뀌는지 단언하세요.
ContentServiceTest의 동일한 교체 패턴을 참고해 UserServiceTest에서도 BinaryContent와
eventPublisher 흐름을 함께 확인하면 됩니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/codeit/team5/mopl/content/controller/ContentController.java`:
- Around line 44-62: `patchContent`와 `deleteContent`가 ADMIN 전용이어야 하는데 현재는
`permitAll()` 설정 때문에 누구나 호출할 수 있습니다. `ContentController`의
`patchContent`/`deleteContent`에 메서드 보안을 적용하는
방식(`@PreAuthorize("hasRole('ADMIN')")`) 또는 `SecurityConfig`에서
`/api/contents/**`를 `hasRole("ADMIN")`으로 제한하는 방식으로 막고, 기존 `ContentController`와
`SecurityConfig` 설정이 OpenAPI의 `[어드민]` 표기와 일치하도록 정리해 주세요.

In `@src/main/java/com/codeit/team5/mopl/content/service/ContentService.java`:
- Around line 104-107: The variable name inside storeThumbnailImage currently
uses profileImage, which is misleading for thumbnail handling. Rename the local
variable in ContentService.storeThumbnailImage to thumbnail (and keep any
related uses aligned) so the identifier matches the method’s purpose and avoids
confusion with profile image logic.
- Around line 77-79: The tag update flow in ContentService currently clears all
tags and reattaches them, which can cause duplicate-key conflicts on the
content_tags composite PK and unnecessary DML. Update the tag handling in
ContentService.update by either computing the diff between existing and
requested tags and only removing/adding changes, or by forcing the deletes to be
flushed before attachTags runs if you keep the clear-and-reinsert approach. Use
the existing clearTags and attachTags methods as the main touchpoints.

In
`@src/main/java/com/codeit/team5/mopl/watcher/repository/WatchingSessionRepository.java`:
- Around line 24-25: The `WatchingSessionRepository.findByContentId(...)` query
is using an entity graph that includes the collection path `content.contentTags`
and `content.contentTags.tag`, which can distort `Window`/`ScrollPosition`
paging boundaries. Update the `@EntityGraph` on `findByContentId(...)` to keep
only the to-one associations (`user`, `content`, `content.thumbnail`,
`content.stats`) and remove the collection graph from this method. Load tags
separately, such as via a follow-up query or batch fetching, so the scrolling
window stays stable.

In `@src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java`:
- Around line 339-369: The update_allBlankTags_throwsException test should also
verify that no partial entity mutation occurs before validation fails. Add a
post-exception assertion on the Content created in the test to confirm its title
remains unchanged, so ContentService.update is forced to keep tag validation
before calling content.update and not leave a modified entity state on failure.
- Around line 312-337: `ContentService.update()` and `delete()` currently only
mark the old `BinaryContent` as `DELETED`, but they do not clean up the
underlying storage object, so the fix should separate the DB status change from
actual blob removal by introducing a storage-delete flow in
`BinaryContentStorage` or a delete event/listener. Update the
`update_withNewThumbnail_oldThumbnailMarkedDeleted`-style coverage to assert the
cleanup path as well as the status transition, and make sure
`ContentService.update()`, `ContentService.delete()`, and the thumbnail
replacement logic in `Content`/`BinaryContent` are wired to the new deletion
mechanism.

---

Outside diff comments:
In `@src/test/java/com/codeit/team5/mopl/user/service/UserServiceTest.java`:
- Around line 199-227: 현재 update_withImage_success()는 새 프로필 이미지만 붙는 경우만 검증해서, 기존
이미지가 있던 사용자의 교체 시 oldProfile이 DELETED로 전환되는 분기를 커버하지 못합니다. UserService.update와
관련된 프로필 이미지 교체 경로를 직접 검증하는 별도 테스트를 추가하거나 기존 테스트를 교체 시나리오로 바꿔서, User.create 대신 기존
profileImage를 가진 User를 준비한 뒤 oldProfile.getUploadStatus()가 DELETED로 바뀌는지 단언하세요.
ContentServiceTest의 동일한 교체 패턴을 참고해 UserServiceTest에서도 BinaryContent와
eventPublisher 흐름을 함께 확인하면 됩니다.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cc20e51e-fa1b-4bda-9e99-61b4302f8290

📥 Commits

Reviewing files that changed from the base of the PR and between 44ea16f and ae254d3.

📒 Files selected for processing (20)
  • src/main/java/com/codeit/team5/mopl/binarycontent/entity/BinaryContentUploadStatus.java
  • src/main/java/com/codeit/team5/mopl/content/controller/ContentController.java
  • src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCreateRequest.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ContentUpdateRequest.java
  • src/main/java/com/codeit/team5/mopl/content/entity/Content.java
  • src/main/java/com/codeit/team5/mopl/content/exception/ContentException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/ContentNotFoundException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/InvalidContentTitleException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/TooManyTagsException.java
  • src/main/java/com/codeit/team5/mopl/content/repository/ContentRepository.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/user/repository/UserRepository.java
  • src/main/java/com/codeit/team5/mopl/user/service/UserService.java
  • src/main/java/com/codeit/team5/mopl/watcher/repository/WatchingSessionRepository.java
  • src/main/resources/db/migration/V11__add_deleted_status_to_binary_contents.sql
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.java
  • src/test/java/com/codeit/team5/mopl/content/repository/ContentRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java
  • src/test/java/com/codeit/team5/mopl/user/service/UserServiceTest.java

Comment thread src/main/java/com/codeit/team5/mopl/content/service/ContentService.java Outdated
@plzslp plzslp changed the title Feature/11 content update feat: 콘텐츠 관리 - 콘텐츠 수정, 삭제 기능 구현 Jun 26, 2026
@plzslp plzslp requested a review from conradrado June 26, 2026 05:45
Comment thread src/main/java/com/codeit/team5/mopl/content/entity/Content.java Outdated
@conradrado

Copy link
Copy Markdown
Collaborator

수고하셨습니다! 전체적으로 잘 작성하신 것 같습니다

conradrado
conradrado previously approved these changes Jun 26, 2026

@conradrado conradrado left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!

@plzslp plzslp merged commit 31d0156 into dev Jun 26, 2026
2 checks passed
@plzslp plzslp deleted the feature/11-content-update branch June 26, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants